
import sys
if sys.version_info >= (3, 13):
    try:
        import numpy._core.overrides
        if hasattr(numpy._core.overrides, 'decorator'):
            original_decorator = numpy._core.overrides.decorator
            
            def fixed_decorator(*args, **kwargs):
                if len(args) == 2 and isinstance(args[1], bytes):
                    args = (args[0], args[1].decode('utf-8'))
                return original_decorator(*args, **kwargs)
                
            numpy._core.overrides.decorator = fixed_decorator
        else:
            from numpy._core.overrides import array_function_dispatch
            numpy._core.overrides.decorator = array_function_dispatch
    except Exception as e:
        print(f"NumPy兼容性修复失败: {str(e)}")
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use('TkAgg')
from abc import ABC, abstractmethod
from typing import Callable, Tuple, List, Optional, Union, Dict
from scipy.sparse import diags
from scipy.sparse.linalg import spsolve
from tqdm import tqdm
import tkinter as tk
from tkinter import ttk, messagebox
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
from ttkthemes import ThemedTk

class Tooltip:
    def __init__(self, widget, text='widget info'):
        self.waittime = 500
        self.wraplength = 180 
        self.widget = widget
        self.text = text
        self.widget.bind("<Enter>", self.enter)
        self.widget.bind("<Leave>", self.leave)
        self.widget.bind("<ButtonPress>", self.leave)
        self.id = None
        self.tw = None

    def enter(self, event=None):
        self.schedule()

    def leave(self, event=None):
        self.unschedule()
        self.hidetip()

    def schedule(self):
        self.unschedule()
        self.id = self.widget.after(self.waittime, self.showtip)

    def unschedule(self):
        id = self.id
        self.id = None
        if id:
            self.widget.after_cancel(id)

    def showtip(self, event=None):
        x = y = 0
        x, y, cx, cy = self.widget.bbox("insert")
        x += self.widget.winfo_rootx() + 25
        y += self.widget.winfo_rooty() + 20
        self.tw = tk.Toplevel(self.widget)
        self.tw.wm_overrideredirect(True)
        self.tw.wm_geometry("+%d+%d" % (x, y))
        label = tk.Label(self.tw, text=self.text, justify='left',
                       background="#ffffff", relief='solid', borderwidth=1,
                       wraplength = self.wraplength)
        label.pack(ipadx=1)

    def hidetip(self):
        tw = self.tw
        self.tw = None
        if tw:
            tw.destroy()


plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

matplotlib.use('TkAgg')
class EquationTemplate:
    """微分方程模板类"""
    def __init__(self):
        self.templates = {
            "ODE": {
                "指数增长": {
                    "description": "dy/dt = a*y",
                    "equation": "a*y",
                    "exact": "y0*np.exp(a*t)",
                    "params": {
                        "a": {"type": "float", "default": 1.0, "description": "增长率"},
                        "y0": {"type": "float", "default": 1.0, "description": "初始值"}
                    }
                },
                "Logistic增长": {
                    "description": "dy/dt = r*y*(1-y/K)",
                    "equation": "r*y*(1-y/K)",
                    "exact": "K/(1+(K/y0-1)*np.exp(-r*t))",
                    "params": {
                        "r": {"type": "float", "default": 1.0, "description": "增长率"},
                        "K": {"type": "float", "default": 10.0, "description": "承载能力"},
                        "y0": {"type": "float", "default": 1.0, "description": "初始值"}
                    }
                },
                "线性ODE": {
                    "description": "dy/dt = a*y + b*t",
                    "equation": "a*y + b*t",
                    "exact": "np.exp(a*t)*(y0 + b/a**2) - b/a*t - b/a**2",
                    "params": {
                        "a": {"type": "float", "default": -1.0, "description": "系数"},
                        "b": {"type": "float", "default": 1.0, "description": "系数"},
                        "y0": {"type": "float", "default": 1.0, "description": "初始值"}
                    }
                }
            },
            "PDE": {
                "泊松方程": {
                    "description": "∇²u = f(x,y)",
                    "equation": "-2 * np.pi**2 * np.exp(np.pi*(x+y)) * (np.sin(np.pi*x)*np.cos(np.pi*y) + np.sin(np.pi*y)*np.cos(np.pi*x))",
                    "exact": "np.exp(np.pi*(x+y)) * np.sin(np.pi*x) * np.sin(np.pi*y)",
                    "params": {
                        "a": {"type": "float", "default": np.pi, "description": "波数"}
                    }
                },
                "一维热传导": {
                    "description": "∂u/∂t = α ∂²u/∂x²",
                    "equation": "np.sin(np.pi * x)",
                    "exact": "np.exp(-alpha*np.pi**2 * t) * np.sin(np.pi * x)",
                    "params": {
                        "alpha": {"type": "float", "default": 1.0, "description": "热扩散系数"}
                    }
                },
                "二维热传导": {
                    "description": "∂u/∂t = α (∂²u/∂x² + ∂²u/∂y²)",
                    "equation": "np.sin(np.pi * x) * np.cos(np.pi * y)",
                    "exact": "np.sin(np.pi * x) * np.cos(np.pi * y) * np.exp(-alpha*np.pi**2 * t / 2)",
                    "params": {
                        "alpha": {"type": "float", "default": 1.0, "description": "热扩散系数"}
                    }
                }
            }
        }
        
        self.current_equation = {
            "type": "ODE",
            "name": "指数增长",
            "equation": "a*y",
            "exact": "y0*np.exp(a*t)",
            "params": {"a": 1.0, "y0": 1.0}
        }

    def get_template(self, eq_type: str, eq_name: str) -> Dict:
        """获取指定模板"""
        return self.templates[eq_type][eq_name]
    
    def get_all_templates(self) -> Dict:
        """获取所有模板"""
        return self.templates
    
    def set_current_equation(self, eq_type: str, eq_name: str, params: Dict):
        """设置当前方程"""
        template = self.get_template(eq_type, eq_name)
        self.current_equation = {
            "type": eq_type,
            "name": eq_name,
            "equation": template["equation"],
            "exact": template["exact"],
            "params": params
        }
    
    def get_current_equation(self) -> Dict:
        """获取当前方程"""
        return self.current_equation
    
    def create_function(self, expr: str, variables: List[str], params: Dict) -> Callable:
        """从字符串表达式创建函数"""
        allowed_names = {
            'np': np,
            'sin': np.sin,
            'cos': np.cos,
            'exp': np.exp,
            'pi': np.pi,
            'sqrt': np.sqrt,
            'tan': np.tan,
            'log': np.log,
            'abs': abs,
            'pow': pow
        }
        allowed_names.update(params)
        allowed_names.update({var: None for var in variables})
        try:
            code = compile(expr, '<string>', 'eval')
        except SyntaxError as e:
            raise ValueError(f"方程表达式语法错误: {str(e)}")
        for name in code.co_names:
            if name not in allowed_names:
                raise NameError(f"使用了不允许的名称: {name}")
        def get_method_type(self, method_name: str) -> str:
            """返回方法适用的方程类型"""
            pde_methods = ["Jacobi方法", "Gauss-Seidel方法", "前向欧拉方法", 
                          "后向欧拉方法", "Crank-Nicolson方法", "ADI方法"]
            return "PDE" if method_name in pde_methods else "ODE"
    
        def validate_equation_method(self, eq_type: str, method_name: str):
            """验证方程与方法是否匹配"""
            method_type = self.get_method_type(method_name)
            if eq_type != method_type:
                raise ValueError(f"方法'{method_name}'不能用于{eq_type}方程")

        def func(*args):
            try:
                local_vars = dict(zip(variables, args))
                local_vars.update({k: v for k, v in allowed_names.items() if v is not None})
                return eval(code, {'__builtins__': {}}, local_vars)
            except Exception as e:
                raise RuntimeError(f"函数执行错误: {str(e)}")
        
        return func

class ODESolver(ABC):
    """ODE求解器的抽象基类"""
    def __init__(self, f: Callable, y0: float, t0: float, t_end: float, h: float):
        self.f = f
        self.y0 = y0
        self.t0 = t0
        self.t_end = t_end
        self.h = h
        self.n_steps = int((t_end - t0) / h)
        self.t = np.linspace(t0, t_end, self.n_steps + 1)
        self.y = np.zeros(self.n_steps + 1)
        self.y[0] = y0
        self.progress_callback = None
        self.iteration_callback = None

    def set_progress_callback(self, callback: Callable):
        self.progress_callback = callback
    def set_iteration_callback(self, callback: Callable):
        self.iteration_callback = callback
    def update_progress(self, progress: int):
        if self.progress_callback:
            self.progress_callback(progress)
    def update_iteration_info(self, info: str):
        if self.iteration_callback:
            self.iteration_callback(info)

    @abstractmethod
    def solve(self) -> Tuple[np.ndarray, np.ndarray]:
        pass

    def get_solution(self) -> Tuple[np.ndarray, np.ndarray]:
        return self.t, self.y

    def get_errors(self, exact_solution: Optional[Callable]) -> Tuple[Optional[float], Optional[float]]:
        if exact_solution is None:
            return None, None
        exact = exact_solution(self.t)
        error = np.abs(self.y - exact)
        return np.max(error), np.mean(error)

class Euler(ODESolver):
    """欧拉方法求解ODE"""
    def solve(self) -> Tuple[np.ndarray, np.ndarray]:
        for i in range(self.n_steps):
            self.y[i+1] = self.y[i] + self.h * self.f(self.t[i], self.y[i])
            self.update_progress(int(100 * (i + 1) / self.n_steps))
            self.update_iteration_info(f"步骤 {i+1}/{self.n_steps}: t={self.t[i]:.4f}, y={self.y[i]:.6f}")
        return self.t, self.y

class ImplicitEuler(ODESolver):
    """隐式欧拉方法求解ODE"""
    def __init__(self, f: Callable, y0: float, t0: float, t_end: float, h: float,
                 max_iter: int = 100, tol: float = 1e-6):
        super().__init__(f, y0, t0, t_end, h)
        self.max_iter = max_iter
        self.tol = tol

    def solve(self) -> Tuple[np.ndarray, np.ndarray]:
        for i in range(self.n_steps):
            y_next = self.y[i] + self.h * self.f(self.t[i], self.y[i])
            for iter in range(self.max_iter):
                y_old = y_next
                y_next = self.y[i] + self.h * self.f(self.t[i+1], y_old)
                if np.abs(y_next - y_old) < self.tol:
                    self.update_iteration_info(f"步骤 {i+1}/{self.n_steps}: 迭代 {iter+1} 收敛")
                    break
                if iter == self.max_iter - 1:
                    self.update_iteration_info(f"步骤 {i+1}/{self.n_steps}: 迭代未收敛")

            self.y[i+1] = y_next
            self.update_progress(int(100 * (i + 1) / self.n_steps))
            self.update_iteration_info(f"步骤 {i+1}/{self.n_steps} 完成: t={self.t[i+1]:.4f}, y={self.y[i+1]:.6f}")
        return self.t, self.y

class Trapezoidal(ODESolver):
    """梯形方法求解ODE"""
    def __init__(self, f: Callable, y0: float, t0: float, t_end: float, h: float,
                 max_iter: int = 100, tol: float = 1e-6):
        super().__init__(f, y0, t0, t_end, h)
        self.max_iter = max_iter
        self.tol = tol

    def solve(self) -> Tuple[np.ndarray, np.ndarray]:
        for i in range(self.n_steps):
            y_next = self.y[i] + self.h * self.f(self.t[i], self.y[i])
            for iter in range(self.max_iter):
                y_old = y_next
                y_next = self.y[i] + self.h/2 * (
                    self.f(self.t[i], self.y[i]) + self.f(self.t[i+1], y_old)
                )
                if np.abs(y_next - y_old) < self.tol:
                    self.update_iteration_info(f"步骤 {i+1}/{self.n_steps}: 迭代 {iter+1} 收敛")
                    break
                if iter == self.max_iter - 1:
                    self.update_iteration_info(f"步骤 {i+1}/{self.n_steps}: 迭代未收敛")

            self.y[i+1] = y_next
            self.update_progress(int(100 * (i + 1) / self.n_steps))
            self.update_iteration_info(f"步骤 {i+1}/{self.n_steps} 完成: t={self.t[i+1]:.4f}, y={self.y[i+1]:.6f}")
        return self.t, self.y

class ImprovedEuler(ODESolver):
    """改进欧拉方法(Heun方法)求解ODE"""
    def solve(self) -> Tuple[np.ndarray, np.ndarray]:
        for i in range(self.n_steps):
            k1 = self.f(self.t[i], self.y[i])
            k2 = self.f(self.t[i] + self.h, self.y[i] + self.h * k1)
            self.y[i+1] = self.y[i] + self.h/2 * (k1 + k2)
            self.update_progress(int(100 * (i + 1) / self.n_steps))
            self.update_iteration_info(f"步骤 {i+1}/{self.n_steps}: t={self.t[i]:.4f}, y={self.y[i]:.6f}, k1={k1:.6f}, k2={k2:.6f}")
        return self.t, self.y

class RungeKutta4(ODESolver):
    """经典四阶龙格-库塔方法"""
    def solve(self) -> Tuple[np.ndarray, np.ndarray]:
        for i in range(self.n_steps):
            t_i = self.t[i]
            y_i = self.y[i]
            
            k1 = self.f(t_i, y_i)
            k2 = self.f(t_i + self.h/2, y_i + self.h/2 * k1)
            k3 = self.f(t_i + self.h/2, y_i + self.h/2 * k2)
            k4 = self.f(t_i + self.h, y_i + self.h * k3)
            
            self.y[i+1] = y_i + self.h/6 * (k1 + 2*k2 + 2*k3 + k4)
            
            self.update_progress(int(100 * (i + 1) / self.n_steps))
            self.update_iteration_info(
                f"步骤 {i+1}/{self.n_steps}: t={t_i:.4f}, y={y_i:.6f}, "
                f"k1={k1:.6f}, k2={k2:.6f}, k3={k3:.6f}, k4={k4:.6f}"
            )
        return self.t, self.y

class AdamsBashforth2(ODESolver):
    """Adams-Bashforth二阶方法"""
    def solve(self) -> Tuple[np.ndarray, np.ndarray]:
        # 第一步使用RK4
        k1 = self.f(self.t[0], self.y[0])
        k2 = self.f(self.t[0] + self.h/2, self.y[0] + self.h/2 * k1)
        k3 = self.f(self.t[0] + self.h/2, self.y[0] + self.h/2 * k2)
        k4 = self.f(self.t[0] + self.h, self.y[0] + self.h * k3)
        self.y[1] = self.y[0] + self.h/6 * (k1 + 2*k2 + 2*k3 + k4)
        self.update_progress(int(100 * 1 / self.n_steps))
        self.update_iteration_info(f"初始步骤完成: t={self.t[1]:.4f}, y={self.y[1]:.6f}")

        for i in range(1, self.n_steps):
            self.y[i+1] = self.y[i] + self.h/2 * (
                3 * self.f(self.t[i], self.y[i]) - self.f(self.t[i-1], self.y[i-1])
            )
            self.update_progress(int(100 * (i + 1) / self.n_steps))
            self.update_iteration_info(f"步骤 {i+1}/{self.n_steps}: t={self.t[i+1]:.4f}, y={self.y[i+1]:.6f}")
        return self.t, self.y

class AdamsMoulton2(ODESolver):
    """Adams-Moulton二阶方法(预测-校正)"""
    def __init__(self, f: Callable, y0: float, t0: float, t_end: float, h: float,
                 max_iter: int = 100, tol: float = 1e-6):
        super().__init__(f, y0, t0, t_end, h)
        self.max_iter = max_iter
        self.tol = tol

    def solve(self) -> Tuple[np.ndarray, np.ndarray]:
        # 第一步使用RK4
        k1 = self.f(self.t[0], self.y[0])
        k2 = self.f(self.t[0] + self.h/2, self.y[0] + self.h/2 * k1)
        k3 = self.f(self.t[0] + self.h/2, self.y[0] + self.h/2 * k2)
        k4 = self.f(self.t[0] + self.h, self.y[0] + self.h * k3)
        self.y[1] = self.y[0] + self.h/6 * (k1 + 2*k2 + 2*k3 + k4)
        self.update_progress(int(100 * 1 / self.n_steps))
        self.update_iteration_info(f"初始步骤完成: t={self.t[1]:.4f}, y={self.y[1]:.6f}")

        for i in range(1, self.n_steps):
            y_pred = self.y[i] + self.h/12 * (
                23 * self.f(self.t[i], self.y[i]) -
                16 * self.f(self.t[i-1], self.y[i-1]) +
                5 * self.f(self.t[i-2], self.y[i-2])
            )
            for iter in range(self.max_iter):
                y_old = y_pred
                y_pred = self.y[i] + self.h/12 * (
                    5 * self.f(self.t[i+1], y_old) +
                    8 * self.f(self.t[i], self.y[i]) -
                    self.f(self.t[i-1], self.y[i-1])
                )
                if np.abs(y_pred - y_old) < self.tol:
                    self.update_iteration_info(f"步骤 {i+1}/{self.n_steps}: 迭代 {iter+1} 收敛")
                    break
                if iter == self.max_iter - 1:
                    self.update_iteration_info(f"步骤 {i+1}/{self.n_steps}: 迭代未收敛")

            self.y[i+1] = y_pred
            self.update_progress(int(100 * (i + 1) / self.n_steps))
            self.update_iteration_info(f"步骤 {i+1}/{self.n_steps} 完成: t={self.t[i+1]:.4f}, y={self.y[i+1]:.6f}")
        return self.t, self.y

class PoissonSolver:
    def __init__(self, f: Callable, exact: Callable, h: float = 1/8,
                 max_iter: int = 10000, tol: float = 1e-6):
        self.f = f
        self.exact = exact
        self.h = h
        self.n = int(1 / h) - 1
        self.x = np.linspace(0, 1, self.n + 2)
        self.y = np.linspace(0, 1, self.n + 2)
        self.X, self.Y = np.meshgrid(self.x, self.y)
        self.max_iter = max_iter
        self.tol = tol
        self.progress_callback = None
        self.iteration_callback = None

    def set_progress_callback(self, callback: Callable):
        self.progress_callback = callback

    def set_iteration_callback(self, callback: Callable):
        self.iteration_callback = callback
    def update_progress(self, progress: int):
        if self.progress_callback:
            self.progress_callback(progress)
    def update_iteration_info(self, info: str):
        if self.iteration_callback:
            self.iteration_callback(info)

    def jacobi(self) -> Tuple[np.ndarray, np.ndarray]:
        """Jacobi迭代方法"""
        u = np.zeros((self.n + 2, self.n + 2))
        f_vals = self.f(self.X, self.Y)
        exact_sol = self.exact(self.X, self.Y)

        for iter in range(self.max_iter):
            u_new = u.copy()
            for i in range(1, self.n + 1):
                for j in range(1, self.n + 1):
                    u_new[i, j] = 0.25 * (
                        u[i-1, j] + u[i+1, j] + u[i, j-1] + u[i, j+1] +
                        self.h**2 * f_vals[i, j]
                    )
            error = np.max(np.abs(u_new - u))
            u = u_new
            self.update_progress(int(100 * (iter + 1) / self.max_iter))
            self.update_iteration_info(f"迭代 {iter+1}/{self.max_iter}: 最大误差={error:.4e}")
            if error < self.tol:
                self.update_iteration_info(f"Jacobi迭代在 {iter+1} 步后收敛")
                break
            if iter == self.max_iter - 1:
                self.update_iteration_info("Jacobi迭代未收敛")
        return u, exact_sol

    def gauss_seidel(self) -> Tuple[np.ndarray, np.ndarray]:
        """Gauss-Seidel迭代方法"""
        u = np.zeros((self.n + 2, self.n + 2))
        f_vals = self.f(self.X, self.Y)
        exact_sol = self.exact(self.X, self.Y)
        for iter in range(self.max_iter):
            u_old = u.copy()
            for i in range(1, self.n + 1):
                for j in range(1, self.n + 1):
                    u[i, j] = 0.25 * (
                        u[i-1, j] + u[i+1, j] + u[i, j-1] + u[i, j+1] +
                        self.h**2 * f_vals[i, j]
                    )

            error = np.max(np.abs(u - u_old))
            self.update_progress(int(100 * (iter + 1) / self.max_iter))
            self.update_iteration_info(f"迭代 {iter+1}/{self.max_iter}: 最大误差={error:.4e}")
            if error < self.tol:
                self.update_iteration_info(f"Gauss-Seidel迭代在 {iter+1} 步后收敛")
                break
            if iter == self.max_iter - 1:
                self.update_iteration_info("Gauss-Seidel迭代未收敛")

        return u, exact_sol

    def get_errors(self, u: np.ndarray) -> Tuple[float, float]:
        """计算误差"""
        exact_sol = self.exact(self.X, self.Y)
        error = np.abs(u - exact_sol)
        return np.max(error), np.mean(error)

class HeatEquationSolver:
    """一维热传导方程求解器"""
    def __init__(self, initial_cond: Callable, exact: Optional[Callable] = None,
                 h: float = 0.1, r: float = 0.1, t_end: float = 0.5):
        self.initial_cond = initial_cond
        self.exact = exact
        self.h = h
        self.r = r
        self.tau = r * h**2
        self.t_end = t_end
        self.n_steps = min(int(t_end / self.tau), 10000)  # 限制步数
        self.x = np.linspace(0, 1, int(1/h) + 1)
        self.t = np.linspace(0, t_end, self.n_steps + 1)
        self.u = np.zeros((len(self.x), self.n_steps + 1))
        self.u[:, 0] = initial_cond(self.x)
        self.progress_callback = None
        self.iteration_callback = None

    def set_progress_callback(self, callback: Callable):
        self.progress_callback = callback

    def set_iteration_callback(self, callback: Callable):
        self.iteration_callback = callback

    def update_progress(self, progress: int):
        if self.progress_callback:
            self.progress_callback(progress)

    def update_iteration_info(self, info: str):
        if self.iteration_callback:
            self.iteration_callback(info)

    def forward_euler(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
        """显式前向欧拉方法"""
        for n in range(self.n_steps):
            for j in range(1, len(self.x)-1):
                self.u[j, n+1] = self.u[j, n] + self.r * (
                    self.u[j+1, n] - 2*self.u[j, n] + self.u[j-1, n]
                )
            self.u[0, n+1] = self.u[0, 0]
            self.u[-1, n+1] = self.u[-1, 0]
            self.update_progress(int(100 * (n + 1) / self.n_steps))
            self.update_iteration_info(f"时间步 {n+1}/{self.n_steps}: t={self.t[n+1]:.4f}")
        return self.x, self.t, self.u

    def backward_euler(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
        """隐式后向欧拉方法"""
        n = len(self.x) - 2
        main_diag = (1 + 2*self.r) * np.ones(n)
        off_diag = -self.r * np.ones(n-1)
        A = diags([off_diag, main_diag, off_diag], [-1, 0, 1])

        for k in range(self.n_steps):
            b = self.u[1:-1, k].copy()
            b[0] += self.r * self.u[0, k]
            b[-1] += self.r * self.u[-1, k]

            self.u[1:-1, k+1] = spsolve(A, b)
            self.u[0, k+1] = self.u[0, 0]
            self.u[-1, k+1] = self.u[-1, 0]
            self.update_progress(int(100 * (k + 1) / self.n_steps))
            self.update_iteration_info(f"时间步 {k+1}/{self.n_steps}: t={self.t[k+1]:.4f}")
        return self.x, self.t, self.u

    def crank_nicolson(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
        """Crank-Nicolson方法"""
        n = len(self.x) - 2
        main_diag = (1 + self.r) * np.ones(n)
        off_diag = -self.r/2 * np.ones(n-1)
        A = diags([off_diag, main_diag, off_diag], [-1, 0, 1])
        main_diag_B = (1 - self.r) * np.ones(n)
        off_diag_B = self.r/2 * np.ones(n-1)
        B = diags([off_diag_B, main_diag_B, off_diag_B], [-1, 0, 1])

        for k in range(self.n_steps):
            b = B.dot(self.u[1:-1, k])
            b[0] += self.r/2 * (self.u[0, k] + self.u[0, k+1])
            b[-1] += self.r/2 * (self.u[-1, k] + self.u[-1, k+1])

            self.u[1:-1, k+1] = spsolve(A, b)
            self.u[0, k+1] = self.u[0, 0]
            self.u[-1, k+1] = self.u[-1, 0]
            self.update_progress(int(100 * (k + 1) / self.n_steps))
            self.update_iteration_info(f"时间步 {k+1}/{self.n_steps}: t={self.t[k+1]:.4f}")
        return self.x, self.t, self.u

    def get_errors(self, method: str, time_step: int = -1) -> Tuple[Optional[float], Optional[float]]:
        if self.exact is None:
            return None, None
        if method == "forward_euler" or method == "前向欧拉方法":
            x, t, u = self.forward_euler()
        elif method == "backward_euler" or method == "后向欧拉方法":
            x, t, u = self.backward_euler()
        elif method == "crank_nicolson" or method == "Crank-Nicolson方法":
            x, t, u = self.crank_nicolson()
        else:
            raise ValueError(f"未知方法: {method}")

        exact = self.exact(x, t[time_step])
        error = np.abs(u[:, time_step] - exact)
        return np.max(error), np.mean(error)

class ADIPoissonSolver:
    def __init__(self, f: Callable, exact: Callable, h: float = 1/40,
                 max_iter: int = 1000, tol: float = 1e-6):
        self.f = f
        self.exact = exact
        self.h = h
        self.n = int(1 / h) - 1
        self.x = np.linspace(0, 1, self.n + 2)
        self.y = np.linspace(0, 1, self.n + 2)
        self.X, self.Y = np.meshgrid(self.x, self.y)
        self.max_iter = max_iter
        self.tol = tol
        self.progress_callback = None
        self.iteration_callback = None

    def set_progress_callback(self, callback: Callable):
        self.progress_callback = callback

    def set_iteration_callback(self, callback: Callable):
        self.iteration_callback = callback

    def update_progress(self, progress: int):
        if self.progress_callback:
            self.progress_callback(progress)

    def update_iteration_info(self, info: str):
        if self.iteration_callback:
            self.iteration_callback(info)

    def solve(self) -> Tuple[np.ndarray, np.ndarray]:
        """使用ADI方法求解"""
        u = np.zeros((self.n + 2, self.n + 2))
        f_vals = self.f(self.X, self.Y)
        exact_sol = self.exact(self.X, self.Y)

        # 创建三对角矩阵
        main_diag = 4 * np.ones(self.n)
        off_diag = -1 * np.ones(self.n - 1)
        A = diags([off_diag, main_diag, off_diag], [-1, 0, 1])

        for iter in range(self.max_iter):
            u_old = u.copy()

            # x方向求解
            for j in range(1, self.n + 1):
                rhs = u_old[j-1, 1:-1] + u_old[j+1, 1:-1] + self.h**2 * f_vals[j, 1:-1]
                u[j, 1:-1] = spsolve(A, rhs)
                
            # y方向求解
            for i in range(1, self.n + 1):
                rhs = u[1:-1, i-1] + u[1:-1, i+1] + self.h**2 * f_vals[1:-1, i]
                u[1:-1, i] = spsolve(A, rhs)

            error = np.max(np.abs(u - u_old))
            self.update_progress(int(100 * (iter + 1) / self.max_iter))
            self.update_iteration_info(f"ADI迭代 {iter+1}/{self.max_iter}: 最大误差={error:.4e}")
            if error < self.tol:
                self.update_iteration_info(f"ADI方法在 {iter+1} 步后收敛")
                break
            if iter == self.max_iter - 1:
                self.update_iteration_info("ADI方法未收敛")

        return u, exact_sol

class ADIHeatSolver:
    """交替方向隐式(ADI)方法求解二维热传导方程"""
    def __init__(self, initial_cond: Callable, exact: Callable,
                 h: float = 1/40, r: float = 1, t_end: float = 1.0):
        self.initial_cond = initial_cond
        self.exact = exact
        self.h = h
        self.r = r
        self.tau = r * h**2
        self.t_end = t_end
        self.n_steps = int(t_end / self.tau)
        self.x = np.linspace(0, 1, int(1/h) + 1)
        self.y = np.linspace(0, 1, int(1/h) + 1)
        self.X, self.Y = np.meshgrid(self.x, self.y)
        self.t = np.linspace(0, t_end, self.n_steps + 1)
        self.u = np.zeros((len(self.x), len(self.y), self.n_steps + 1))
        self.u[:, :, 0] = initial_cond(self.X, self.Y)
        self.progress_callback = None
        self.iteration_callback = None

    def set_progress_callback(self, callback: Callable):
        """设置进度回调函数"""
        self.progress_callback = callback

    def set_iteration_callback(self, callback: Callable):
        """设置迭代信息回调函数"""
        self.iteration_callback = callback

    def update_progress(self, progress: int):
        if self.progress_callback:
            self.progress_callback(progress)

    def update_iteration_info(self, info: str):
        if self.iteration_callback:
            self.iteration_callback(info)

    def solve(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
        """使用ADI方法求解"""
        n = len(self.x) - 2
        main_diag = (1 + self.r) * np.ones(n)
        off_diag = -self.r/2 * np.ones(n-1)
        A = diags([off_diag, main_diag, off_diag], [-1, 0, 1])
        B = diags([off_diag, main_diag, off_diag], [-1, 0, 1])

        for k in range(self.n_steps):
            u_half = np.zeros_like(self.u[:, :, 0])
            for j in range(1, len(self.y)-1):
                rhs = (1 - self.r) * self.u[1:-1, j, k] + self.r/2 * (
                    self.u[1:-1, j+1, k] + self.u[1:-1, j-1, k]
                )
                u_half[1:-1, j] = spsolve(A, rhs)
            for i in range(1, len(self.x)-1):
                rhs = (1 - self.r) * u_half[i, 1:-1] + self.r/2 * (
                    u_half[i+1, 1:-1] + u_half[i-1, 1:-1]
                )
                self.u[i, 1:-1, k+1] = spsolve(B, rhs)

            self.u[0, :, k+1] = self.u[0, :, 0]
            self.u[-1, :, k+1] = self.u[-1, :, 0]
            self.u[:, 0, k+1] = self.u[:, 0, 0]
            self.u[:, -1, k+1] = self.u[:, -1, 0]
            self.update_progress(int(100 * (k + 1) / self.n_steps))
            self.update_iteration_info(f"时间步 {k+1}/{self.n_steps}: t={self.t[k+1]:.4f}")
        return self.X, self.Y, self.t, self.u

    def get_errors(self, time_step: int = -1) -> Tuple[float, float]:
        """计算误差"""
        exact = self.exact(self.X, self.Y, self.t[time_step])
        error = np.abs(self.u[:, :, time_step] - exact)
        return np.max(error), np.mean(error)


class NumericalMethodsApp:
    def __init__(self, root):
        self.root = root
        self.root.title("微分方程数值求解器")
        self.root.geometry("1200x800")
        self.root.minsize(1000, 700)
        self.root.set_theme("arc")
        
        # 初始化方程模板
        self.equation_template = EquationTemplate()
        self.current_equation = {
            "type": "ODE",
            "name": "指数增长",
            "params": {"a": 1.0, "y0": 1.0}
        }
        
        # 主界面布局
        self.main_frame = ttk.Frame(root)
        self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 控制面板
        self.control_frame = ttk.LabelFrame(self.main_frame, text="控制面板", width=350)
        self.control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
        
        # 方程选择
        self.create_equation_controls()
        
        # 方法选择
        self.create_method_controls()
        
        # 参数输入
        self.param_frame = ttk.LabelFrame(self.control_frame, text="参数设置")
        self.param_frame.pack(pady=10, padx=5, fill=tk.X)
        self.create_parameter_inputs()
        
        # 求解按钮
        self.solve_button = ttk.Button(self.control_frame, text="求解", command=self.solve)
        self.solve_button.pack(pady=10, padx=10, fill=tk.X)
        
        # 进度条
        self.progress_bar = ttk.Progressbar(self.control_frame, orient="horizontal", length=320, mode="determinate")
        self.progress_bar.pack(pady=5, padx=10, fill=tk.X)
        
        # 右侧主区域（绘图+结果输出）
        self.right_frame = ttk.Frame(self.main_frame)
        self.right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 绘图区域（上方）
        self.plot_frame = ttk.Frame(self.right_frame)
        self.plot_frame.pack(fill=tk.BOTH, expand=True)
        
        self.figure = Figure(figsize=(8, 6), dpi=100)
        self.canvas = FigureCanvasTkAgg(self.figure, master=self.plot_frame)
        self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        
        self.toolbar = NavigationToolbar2Tk(self.canvas, self.plot_frame)
        self.toolbar.update()
        
        # 结果输出区域（下方）
        self.result_frame = ttk.LabelFrame(self.right_frame, text="结果输出", height=200)
        self.result_frame.pack(fill=tk.BOTH, expand=False, pady=(5,0))
        
        self.result_text = tk.Text(self.result_frame, height=10, wrap=tk.WORD, font=('微软雅黑', 9))
        self.result_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.result_text.tag_config("blue", foreground="blue")
        self.result_text.tag_config("red", foreground="red")
        
        scrollbar = ttk.Scrollbar(self.result_frame, orient="vertical", command=self.result_text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.result_text.config(yscrollcommand=scrollbar.set)
        
        # 初始化
        self.update_methods()
        self.update_parameter_inputs()
    def create_equation_controls(self):
        """创建方程选择控件"""
        frame = ttk.LabelFrame(self.control_frame, text="方程设置")
        frame.pack(pady=10, padx=5, fill=tk.X)
        
        # 方程类型
        ttk.Label(frame, text="方程类型:").pack(pady=(5, 0))
        self.eq_type_var = tk.StringVar(value="ODE")
        self.eq_type_combo = ttk.Combobox(frame, textvariable=self.eq_type_var, 
                                        values=["ODE", "PDE"], state="readonly")
        self.eq_type_combo.pack(pady=5, fill=tk.X)
        self.eq_type_combo.bind("<<ComboboxSelected>>", self.on_equation_type_changed)
        
        # 方程选择
        ttk.Label(frame, text="选择方程:").pack(pady=(5, 0))
        self.eq_name_var = tk.StringVar()
        self.eq_name_combo = ttk.Combobox(frame, textvariable=self.eq_name_var, state="readonly")
        self.eq_name_combo.pack(pady=5, fill=tk.X)
        self.eq_name_combo.bind("<<ComboboxSelected>>", self.on_equation_changed)
        
        # 方程描述
        self.eq_desc_label = ttk.Label(frame, text="", wraplength=320)
        self.eq_desc_label.pack(pady=5)

    def create_method_controls(self):
        """创建方法选择控件"""
        frame = ttk.LabelFrame(self.control_frame, text="数值方法")
        frame.pack(pady=10, padx=5, fill=tk.X)
        
        ttk.Label(frame, text="选择方法:").pack(pady=(5, 0))
        self.method_var = tk.StringVar()
        self.method_combo = ttk.Combobox(frame, textvariable=self.method_var, state="readonly")
        self.method_combo.pack(pady=5, fill=tk.X)

    def create_parameter_inputs(self):
        """创建参数输入控件"""
        for widget in self.param_frame.winfo_children():
            widget.destroy()
            
        # 通用参数
        ttk.Label(self.param_frame, text="初始值 y0:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=2)
        self.y0_entry = ttk.Entry(self.param_frame)
        self.y0_entry.grid(row=0, column=1, padx=5, pady=2)
        
        ttk.Label(self.param_frame, text="初始时间 t0:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)
        self.t0_entry = ttk.Entry(self.param_frame)
        self.t0_entry.grid(row=1, column=1, padx=5, pady=2)
        
        ttk.Label(self.param_frame, text="结束时间 t_end:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=2)
        self.t_end_entry = ttk.Entry(self.param_frame)
        self.t_end_entry.grid(row=2, column=1, padx=5, pady=2)
        
        ttk.Label(self.param_frame, text="步长 h:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=2)
        self.h_entry = ttk.Entry(self.param_frame)
        self.h_entry.grid(row=3, column=1, padx=5, pady=2)
        
        ttk.Label(self.param_frame, text="最大迭代次数:").grid(row=4, column=0, sticky=tk.W, padx=5, pady=2)
        self.max_iter_entry = ttk.Entry(self.param_frame)
        self.max_iter_entry.grid(row=4, column=1, padx=5, pady=2)
        
        ttk.Label(self.param_frame, text="容差 tol:").grid(row=5, column=0, sticky=tk.W, padx=5, pady=2)
        self.tol_entry = ttk.Entry(self.param_frame)
        self.tol_entry.grid(row=5, column=1, padx=5, pady=2)
        
        ttk.Label(self.param_frame, text="r值:").grid(row=6, column=0, sticky=tk.W, padx=5, pady=2)
        self.r_entry = ttk.Entry(self.param_frame)
        self.r_entry.grid(row=6, column=1, padx=5, pady=2)
        
        # 方程特定参数
        self.eq_param_entries = {}
        self.eq_param_frame = ttk.Frame(self.param_frame)
        self.eq_param_frame.grid(row=7, column=0, columnspan=2, sticky=tk.W, padx=5, pady=5)

    def on_equation_type_changed(self, event=None):
        """方程类型改变时的处理"""
        eq_type = self.eq_type_var.get()
        templates = self.equation_template.get_all_templates()[eq_type]
        self.eq_name_combo["values"] = list(templates.keys())
        if templates:
            self.eq_name_combo.current(0)
            self.on_equation_changed()

    def on_equation_changed(self, event=None):
        """方程改变时的处理"""
        eq_type = self.eq_type_var.get()
        eq_name = self.eq_name_var.get()
        template = self.equation_template.get_template(eq_type, eq_name)
        
        # 更新描述
        self.eq_desc_label.config(text=template["description"])
        
        # 更新当前方程
        self.current_equation = {
            "type": eq_type,
            "name": eq_name,
            "params": {k: v["default"] for k, v in template["params"].items()}
        }
        
        # 更新方法选择
        self.update_methods()
        
        # 更新参数输入
        self.update_parameter_inputs()

    def update_methods(self):
        """更新方法选择"""
        current_eq = self.current_equation
        
        if current_eq['type'] == "ODE":
            methods = [
                "欧拉方法", "隐式欧拉方法", "梯形方法",
                "改进欧拉方法", "四阶龙格-库塔方法",
                "Adams-Bashforth二阶方法", "Adams-Moulton二阶方法"
            ]
        else:  # PDE
            if "热传导" in current_eq['name']:
                methods = ["前向欧拉方法", "后向欧拉方法", "Crank-Nicolson方法"]
                if "二维" in current_eq['name']:
                    methods.append("ADI方法")
            else:  # 泊松方程
                methods = ["Jacobi方法", "Gauss-Seidel方法", "ADI方法"]
        
        self.method_combo['values'] = methods
        if methods:
            self.method_combo.current(0)

    def update_parameter_inputs(self):
        """更新参数输入"""
        # 设置通用参数默认值
        self.y0_entry.delete(0, tk.END)
        self.y0_entry.insert(0, "1.0")
        
        self.t0_entry.delete(0, tk.END)
        self.t0_entry.insert(0, "0.0")
        
        self.t_end_entry.delete(0, tk.END)
        self.t_end_entry.insert(0, "1.0")
        
        self.h_entry.delete(0, tk.END)
        self.h_entry.insert(0, "0.1")
        
        self.max_iter_entry.delete(0, tk.END)
        self.max_iter_entry.insert(0, "100")
        
        self.tol_entry.delete(0, tk.END)
        self.tol_entry.insert(0, "1e-6")
        
        self.r_entry.delete(0, tk.END)
        self.r_entry.insert(0, "0.1")
        for widget in self.eq_param_frame.winfo_children():
            widget.destroy()
    
        current_eq = self.current_equation
        template = self.equation_template.get_template(current_eq['type'], current_eq['name'])
        
        self.eq_param_entries = {}
        row = 0
        for param, info in template["params"].items():
            ttk.Label(self.eq_param_frame, text=f"{info['description']} ({param}):").grid(
                row=row, column=0, sticky=tk.W, padx=5, pady=2)
            entry = ttk.Entry(self.eq_param_frame)
            entry.grid(row=row, column=1, padx=5, pady=2)
            entry.insert(0, str(info["default"]))
            self.eq_param_entries[param] = entry
            row += 1

    def get_parameters(self):
        """获取所有输入参数"""
        try:
            y0 = float(self.y0_entry.get())
            t0 = float(self.t0_entry.get())
            t_end = float(self.t_end_entry.get())
            h = float(self.h_entry.get())
            max_iter = int(self.max_iter_entry.get())
            tol = float(self.tol_entry.get())
            r = float(self.r_entry.get())
            
            # 方程特定参数
            current_eq = self.current_equation
            template = self.equation_template.get_template(current_eq['type'], current_eq['name'])
            params = {}
            for param, entry in self.eq_param_entries.items():
                params[param] = float(entry.get())
            
            # 参数验证
            if h <= 0 or t_end <= t0 or max_iter <= 0 or tol <= 0 or r <= 0:
                raise ValueError("参数必须为正数")
            
            return y0, t0, t_end, h, max_iter, tol, r, params
        except ValueError as e:
            messagebox.showerror("错误", f"参数输入无效: {str(e)}")
            return None

    def solve(self):
        """执行求解"""
        params = self.get_parameters()
        if params is None:
            return
            
        y0, t0, t_end, h, max_iter, tol, r, eq_params = params
        method = self.method_var.get()
        current_eq = self.current_equation
        
        self.result_text.delete(1.0, tk.END)
        self.result_text.insert(tk.END, f"正在求解: {current_eq['type']} - {method}...\n")
        self.progress_bar['value'] = 0
        self.root.update()

        try:
            if current_eq['type'] == "ODE":
                self.solve_ode(current_eq, method, y0, t0, t_end, h, max_iter, tol, eq_params)
            else:
                if "热传导" in current_eq['name']:
                    self.solve_heat_equation(current_eq, method, h, r, t_end, max_iter, tol, eq_params)
                else:
                    self.solve_poisson_equation(current_eq, method, h, max_iter, tol, eq_params)
            
            self.result_text.insert(tk.END, "求解完成!\n", "blue")
            
        except Exception as e:
            self.handle_error(e)

    def solve_ode(self, current_eq, method, y0, t0, t_end, h, max_iter, tol, eq_params):
        """求解ODE"""
        f = self.equation_template.create_function(
            self.equation_template.get_template(current_eq['type'], current_eq['name'])["equation"],
            ["t", "y"], eq_params)
        
        # 创建求解器
        solver_classes = {
            "欧拉方法": Euler,
            "隐式欧拉方法": ImplicitEuler,
            "梯形方法": Trapezoidal,
            "改进欧拉方法": ImprovedEuler,
            "四阶龙格-库塔方法": RungeKutta4,
            "Adams-Bashforth二阶方法": AdamsBashforth2,
            "Adams-Moulton二阶方法": AdamsMoulton2
        }
        
        solver = solver_classes[method](f, y0, t0, t_end, h)
        
        if hasattr(solver, 'set_max_iter'):
            solver.set_max_iter(max_iter)
        if hasattr(solver, 'set_tolerance'):
            solver.set_tolerance(tol)
            
        solver.set_progress_callback(self.update_progress)
        solver.set_iteration_callback(self.update_iteration_info)

        t, y = solver.solve()
        self.figure.clear()
        ax = self.figure.add_subplot(111)
        ax.plot(t, y, 'b-', label='数值解')
        
        # 绘制精确解(如果存在)
        template = self.equation_template.get_template(current_eq['type'], current_eq['name'])
        if "exact" in template:
            exact = self.equation_template.create_function(
                template["exact"], ["t"], eq_params)
            exact_vals = exact(t)
            ax.plot(t, exact_vals, 'r--', label='精确解')
            ax.fill_between(t, y, exact_vals, alpha=0.2)
        
        ax.set_xlabel('t')
        ax.set_ylabel('y(t)')
        ax.set_title(f'{method} 解')
        ax.legend()
        ax.grid(True)
        self.canvas.draw()

    def solve_heat_equation(self, current_eq, method, h, r, t_end, max_iter, tol, eq_params):
        """求解热传导方程"""
        template = self.equation_template.get_template(current_eq['type'], current_eq['name'])
        
        if "一维" in current_eq['name']:
            initial_cond = self.equation_template.create_function(
                template["equation"], ["x"], eq_params)
            
            exact = None
            if "exact" in template:
                exact = self.equation_template.create_function(
                    template["exact"], ["x", "t"], eq_params)
            
            solver = HeatEquationSolver(initial_cond, exact, h=h, r=r, t_end=t_end)
            solver.set_progress_callback(self.update_progress)
            solver.set_iteration_callback(self.update_iteration_info)
            
            if method == "前向欧拉方法":
                x, t, u = solver.forward_euler()
            elif method == "后向欧拉方法":
                x, t, u = solver.backward_euler()
            elif method == "Crank-Nicolson方法":
                x, t, u = solver.crank_nicolson()
            
            self.figure.clear()
            ax = self.figure.add_subplot(111)
            ax.plot(x, u[:, -1], 'b-', label=f'数值解 (t={t_end})')
            
            if exact is not None:
                exact_final = exact(x, t[-1])
                ax.plot(x, exact_final, 'r--', label=f'精确解 (t={t_end})')
                ax.fill_between(x, u[:, -1], exact_final, alpha=0.2)
            
            ax.set_xlabel('x')
            ax.set_ylabel('u(x,t)')
            
        else:
            initial_cond = self.equation_template.create_function(
                template["equation"], ["x", "y"], eq_params)
            
            exact = None
            if "exact" in template:
                exact = self.equation_template.create_function(
                    template["exact"], ["x", "y", "t"], eq_params)
            
            solver = ADIHeatSolver(initial_cond, exact, h=h, r=r, t_end=t_end)
            solver.set_progress_callback(self.update_progress)
            solver.set_iteration_callback(self.update_iteration_info)
            
            X, Y, t, u = solver.solve()
            
            self.figure.clear()
            ax = self.figure.add_subplot(111, projection='3d')
            surf = ax.plot_surface(X, Y, u[:, :, -1], cmap='viridis')
            ax.set_xlabel('x')
            ax.set_ylabel('y')
            ax.set_zlabel('u(x,y,t)')
            self.figure.colorbar(surf)
        
        ax.set_title(f'{method} 解 (t={t_end})')
        ax.legend()
        ax.grid(True)
        self.canvas.draw()

    def solve_poisson_equation(self, current_eq, method, h, max_iter, tol, eq_params):
        """求解泊松方程"""
        template = self.equation_template.get_template(current_eq['type'], current_eq['name'])
        
        f = self.equation_template.create_function(
            template["equation"], ["x", "y"], eq_params)
        
        exact = None
        if "exact" in template:
            exact = self.equation_template.create_function(
                template["exact"], ["x", "y"], eq_params)
        
        if method == "ADI方法":
            solver = ADIPoissonSolver(f, exact, h=h, max_iter=max_iter, tol=tol)
        else:
            solver = PoissonSolver(f, exact, h=h, max_iter=max_iter, tol=tol)
        
        solver.set_progress_callback(self.update_progress)
        solver.set_iteration_callback(self.update_iteration_info)
        
        if method == "Jacobi方法":
            u, exact_sol = solver.jacobi()
        elif method == "Gauss-Seidel方法":
            u, exact_sol = solver.gauss_seidel()
        elif method == "ADI方法":
            u, exact_sol = solver.solve()
        
        self.figure.clear()
        ax = self.figure.add_subplot(111, projection='3d')
        X, Y = solver.X, solver.Y
        ax.plot_surface(X, Y, u, cmap='viridis')
        ax.set_xlabel('x')
        ax.set_ylabel('y')
        ax.set_zlabel('u(x,y)')
        ax.set_title(f'{method} 解')
        self.canvas.draw()

    def handle_error(self, error):
        """统一错误处理"""
        messagebox.showerror("错误", f"求解过程中发生错误: {str(error)}")
        self.result_text.insert(tk.END, f"错误: {str(error)}\n", "red")
        import traceback
        traceback.print_exc()

    def update_iteration_info(self, info):

        self.result_text.insert(tk.END, info + "\n")
        self.result_text.see(tk.END)
        self.root.update()

    def update_progress(self, value):
        self.progress_bar['value'] = value
        self.root.update()
def main():
    root = ThemedTk(theme="arc")
    app = NumericalMethodsApp(root)
    root.mainloop()

if __name__ == "__main__":
    main()